
#include <BStyle>
#include <BPainter>
#include <BKeyEvent>
#include <BMouseEvent>
#include <BSystem>

#include "member_BSpinBox.h"
#include "member_BStyle.h"

using namespace BWE;

#define member					(*(member_BSpinBox*)_ptr)
#define member_allocate()		_ptr = new member_BSpinBox(this)
#define member_release()		delete (member_BSpinBox*)_ptr

BSpinBox::BSpinBox(BWidget* parent) : BWidget(parent)
{
	member_allocate();
	this->setRange(INT_MIN, INT_MAX);
	this->setCursor(Cursor_Ibeam);
}
BSpinBox::BSpinBox(int minimum, int maximum, BWidget* parent) : BWidget(parent)
{
	member_allocate();
	this->setRange(minimum, maximum);
	this->setCursor(Cursor_Ibeam);
}
BSpinBox::~BSpinBox()
{
	member_release();
}

void BSpinBox::setAlign(Align align)
{
	if (member.align != align)
	{
		member.align = align;
		member.dirty = true;
		this->fresh();
	}
}
Align BSpinBox::align() const
{
	return member.align;
}

bool BSpinBox::setValue(int value)
{
	value = member.range.limit(value);
	if (member.value != value)
	{
		member.value = value;
		member.block.text.reset(value);
		member.block.reset();
		member.dirty = true;
		emit(Signal_ValueChanged, member.value);
		this->fresh();
		return true;
	}
	return false;
}
int BSpinBox::value() const
{
	return member.value;
}

void BSpinBox::setMinimum(int minimum)
{
	setRange(minimum, member.range.max());
}
int BSpinBox::minimum() const
{
	return member.range.min();
}

void BSpinBox::setMaximum(int maximum)
{
	setRange(member.range.min(), maximum);
}
int BSpinBox::maximum() const
{
	return member.range.max();
}

void BSpinBox::setRange(int minimum, int maximum)
{
	setRange(BRange(minimum, maximum));
}
void BSpinBox::setRange(const BRange& range)
{
	if (member.range != range)
	{
		member.range = range;
		int value = member.range.limit(member.value);
		this->setValue(value);
		emit(Signal_RangeChanged, member.range);

		BString minStr(member.range.min());
		BString maxStr(member.range.max());
		member.bitNum = bMax(minStr.size(), maxStr.size());
	}
}
const BRange& BSpinBox::range() const
{
	return member.range;
}

void BSpinBox::setPrefix(const BString& prefix)
{
	if (member.prefix != prefix)
	{
		member.prefix = prefix;
		member.dirtySize = true;
		member.dirty = true;
		this->fresh();
	}
}
const BString& BSpinBox::prefix() const
{
	return member.prefix;
}

void BSpinBox::setPrefixAlign(Align prefixAlign)
{
	if (member.prefixAlign != prefixAlign)
	{
		member.prefixAlign = prefixAlign;
		if (member.prefix.size())
		{
			member.dirty = true;
			this->fresh();
		}
	}
}
Align BSpinBox::prefixAlign() const
{
	return member.prefixAlign;
}

void BSpinBox::setSuffix(const BString& suffix)
{
	if (member.suffix != suffix)
	{
		member.suffix = suffix;
		member.dirtySize = true;
		member.dirty = true;
		this->fresh();
	}
}
const BString& BSpinBox::suffix() const
{
	return member.suffix;
}

void BSpinBox::setSuffixAlign(Align suffixAlign)
{
	if (member.suffixAlign != suffixAlign)
	{
		member.suffixAlign = suffixAlign;
		if (member.suffix.size())
		{
			member.dirty = true;
			this->fresh();
		}
	}
}
Align BSpinBox::suffixAlign() const
{
	return member.suffixAlign;
}

void BSpinBox::setStep(int step)
{
	if (step < 0)
		return;
	if (member.step != step)
	{
		member.step = step;
		emit(Signal_StepChanged, step);
	}
}
int BSpinBox::step() const
{
	return member.step;
}

void BSpinBox::increase()
{
	int value = member.value + member.step;
	this->setValue(value);
}
void BSpinBox::decrease()
{
	int value = member.value - member.step;
	this->setValue(value);
}

void BSpinBox::setFormulable(bool formulable)
{
	if (member.formulable != formulable)
	{
		member.formulable = formulable;
	}
}
bool BSpinBox::formulable() const
{
	return member.formulable;
}

void BSpinBox::setReadOnly(bool readOnly)
{
	if (member.readOnly != readOnly)
	{
		member.readOnly = readOnly;
		if (readOnly)
			this->setCursor(Cursor_None);
		else
			this->setCursor(Cursor_Ibeam);
		if (member.upButton)
		{
			member.upButton->setEnabled(false);
			member.downButton->setEnabled(false);
		}
		this->emit(Signal_ReadOnly, readOnly);
		this->fresh();
	}
}
bool BSpinBox::readOnly() const
{
	return member.readOnly;
}

void BSpinBox::setArrowVisible(bool arrowVisible)
{
	if (!member.upButton && arrowVisible)
	{
		member.upButton = new BButton(Graph_Arrow_Up_Fill, this);
		member.upButton->setName("up");
		member.upButton->setForm(Form_IconOnly);

		member.downButton = new BButton(Graph_Arrow_Down_Fill, this);
		member.downButton->setName("down");
		member.downButton->setForm(Form_IconOnly);

		member.connect(member.upButton, Signal_Clicked, &member_BSpinBox::slotButtonClicked);
		member.connect(member.downButton, Signal_Clicked, &member_BSpinBox::slotButtonClicked);
		member.dirty = true;
		this->fresh();
		return;
	}
	if (member.upButton && !arrowVisible)
	{
		this->removeChild(member.upButton);
		this->removeChild(member.downButton);
		member.dirty = true;
		this->fresh();
	}
}
bool BSpinBox::arrowVisible() const
{
	return member.upButton.valid();
}

void BSpinBox::setWheelEnabled(bool wheelEnabled)
{
	if (member.wheelEnabled != wheelEnabled)
	{
		member.wheelEnabled = wheelEnabled;
	}
}
bool BSpinBox::wheelEnabled() const
{
	return member.wheelEnabled;
}

BSize BSpinBox::sizeHint() const
{
	BSize hsize = BWidget::sizeHint();
	const BFont* font = this->realFont();
	if (font)
	{
		int cw = font->codeWidth('0');
		hsize.width() += cw * member.bitNum;
		hsize.height() += font->size();
		if (member.dirtySize)
		{
			member.freshSizes();
			member.dirtySize = false;
		}
		if (member.prefixSize.valid())
		{
			hsize.width() += member.prefixSize.width();
			hsize.height() = bMax(hsize.height(), member.prefixSize.height());
		}
		if (member.suffixSize.valid())
		{
			hsize.width() += member.suffixSize.width();
			hsize.height() = bMax(hsize.height(), member.suffixSize.height());
		}
	}
	return hsize;
}

bool BSpinBox::input(const BString& text, const BValue& value)
{
	if (text == "edit")
	{
		if (const char* cstr = value)
		{
			member.block.text.reset();
			member.block.text << cstr;
		}
		else if (const BString* str = value)
		{
			member.block.text.reset();
			member.block.text << *str;
		}
		else if (BCode* code = value)
		{
			member.block.text.reset();
			member.block.text << *code;
		}
		member.block.reset();
		member.block.seekTail();
		member.value = member.block.text.toInt();
		member.dirty = true;
		member.editing = true;
		this->fresh();
		return true;
	}
	if (text == "text")
	{
		if (const char* cstr = value)
		{
			member.block.text.reset();
			member.block.text << cstr;
		}
		else if (const BString* str = value)
		{
			member.block.text.reset();
			member.block.text << *str;
		}
		else if (BCode* code = value)
		{
			member.block.text.reset();
			member.block.text << *code;
		}
		member.block.reset();
		member.value = member.block.text.toInt();
		member.dirty = true;
		this->fresh();
		return true;
	}
	if (text == "prefix")
	{
		if (const char* cstr = value)
			member.prefix = cstr;
		else if (const BString* str = value)
			member.prefix = *str;
		else if (BCode* code = value)
			member.prefix = *code;
		member.dirtySize = true;
		member.dirty = true;
		this->fresh();
		return true;
	}
	if (text == "suffix")
	{
		if (const char* cstr = value)
			member.prefix = cstr;
		else if (const BString* str = value)
			member.prefix = *str;
		else if (BCode* code = value)
			member.prefix = *code;
		member.dirtySize = true;
		member.dirty = true;
		this->fresh();
		return true;
	}
	return BWidget::input(text, value);
}
bool BSpinBox::query(const BString& text, BValue& value) const
{
	if (text == "value")
	{
		value = member.value;
		return true;
	}
	if (text == "text")
	{
		value = member.block.text;
		return true;
	}
	if (text == "text-size")
	{
		value = member.block.rect.size();
		return true;
	}
	if (text == "text-rect")
	{
		value = member.block.rect + member.block.rect.pos();
		return true;
	}
	if (text == "prefix")
	{
		value = member.prefix;
		return true;
	}
	if (text == "prefix-rect")
	{
		value = member.prefixRect;
		return true;
	}
	if (text == "suffix")
	{
		value = member.suffix;
		return true;
	}
	if (text == "suffix-rect")
	{
		value = member.suffixRect;
		return true;
	}
	if (text == "code-rects")
	{
		BPoint pos = member.block.rect.pos();
		BRectArray& codeRects = value.fill<BRectArray>();
		codeRects.reset(member.block.cells.size());
		for (int i = 0; i < member.block.cells.size(); i++)
		{
			codeRects[i] = member.block.cells[i].rect + pos;
		}
		return true;
	}
	return BWidget::query(text, value);
}

void BSpinBox::updateEvent(const BEvent& event)
{
	if (this->focused() && member.block.count == 0)
	{
		bool blinkShown = true;
		if (member.blinkTime > 0)
		{
			BReal now = event.time();
			BReal time = now - member.blinkTick;
			blinkShown = time <= member.blinkTime / 2;
			if (time > member.blinkTime)
				member.blinkTick = now;
		}
		if (member.blinkShown != blinkShown)
		{
			member.blinkShown = blinkShown;
			this->fresh();
		}
	}
}
void BSpinBox::resizeEvent(const BEvent& event)
{
	member.dirty = true;
	BWidget::resizeEvent(event);
}

void BSpinBox::styleEvent(const BEvent& event)
{
	BWidget::styleEvent(event);
	member.prefixStyle = 0;
	member.suffixStyle = 0;

	const BStyle* style = event.value();
	style_member(style)->assign(Value_Align, member.align);
	style_member(style)->assign(Value_Line_Width, member.lineWidth);

	if (const BStyle* prefixStyle = style->annex("prefix"))
	{
		member.prefixStyle = prefixStyle;
		style_member(prefixStyle)->assign(Value_Text, member.prefix);
		style_member(prefixStyle)->assign(Value_Align, member.prefixAlign);
	}
	if (const BStyle* suffixStyle = style->annex("suffix"))
	{
		member.suffixStyle = suffixStyle;
		style_member(suffixStyle)->assign(Value_Text, member.suffix);
		style_member(suffixStyle)->assign(Value_Align, member.suffixAlign);
	}

	member.block.font = this->realFont();
	member.block.dirty = true;
	member.dirtySize = true;
	member.dirty = true;
}
void BSpinBox::freshEvent(const BEvent& event)
{
	if (member.dirtySize)
	{
		member.freshSizes();
		member.dirtySize = false;
	}
	if (member.dirty)
	{
		member.freshRects();
		member.dirty = false;
	}
	BWidget::freshEvent(event);
}
void BSpinBox::paintEvent(const BEvent& event)
{
	BPainter painter(this);
	painter.drawBackground();
	painter.drawForeground();

	State state = painter.state();
	if (this->focused() && member.block.index >= 0 && member.block.count)
	{
		const BRect& crect = this->centerRect();
		BPoint pos = member.block.rect.pos();
		painter.setColor(Color_Selection, state);
		int begin = bMin(member.block.index, member.block.index + member.block.count);
		int end = bMax(member.block.index, member.block.index + member.block.count);
		for (int i = begin; i < end; i++)
		{
			const ICodeCell& cell = member.block.cells[i];
			BRect cellRect = cell.rect + pos;
			painter.fillRect(cellRect);
		}
	}

	if (member.prefix.size())
	{
		painter.setStyle(member.prefixStyle);
		if (painter.setColor(Color_Text, state))
		{
			if (member.t_prefix.size())
				painter.drawText(member.prefixRect, member.t_prefix);
			else
				painter.drawText(member.prefixRect, member.prefix);
		}
	}

	if (member.suffix.size())
	{
		painter.setStyle(member.suffixStyle);
		if (painter.setColor(Color_Text, state))
		{
			if (member.t_suffix.size())
				painter.drawText(member.suffixRect, member.t_suffix);
			else
				painter.drawText(member.suffixRect, member.suffix);
		}
	}

	painter.setStyle(0);
	if (painter.setColor(Color_Text, state))
		member.block.paint(painter);

	painter.drawBorder();
	painter.drawPerch();
	painter.drawMargin();

	if (!member.readOnly && member.blinkShown && member.block.count == 0)
	{
		painter.setLineWidth(member.lineWidth);
		painter.setColor(Color_Indicator);
		BLine2 line = member.inputLine();
		painter.drawLine(line.begin(), line.end());
	}
}

void BSpinBox::focusEvent(const BEvent& event)
{
	if (this->focused())
	{
		member.focusedTime = event.time();
		member.block.index = 0;
		member.block.count = member.block.cells.size();
		member.dirty = true;
	}
	else
	{
		if (member.editing)
		{
			member.block.text.reset(member.value);
			member.block.reset();
			member.editing = false;
			emit(Signal_EditFinished, member.value);
			member.dirty = true;
		}
		member.blinkShown = false;
	}
	this->fresh();
	BWidget::focusEvent(event);
}
void BSpinBox::inputEvent(const BEvent& event)
{
	if (member.readOnly == false)
	{
		const BCode& code = event.value();
		bool valid = false;
		if (member.formulable)
		{
			valid = (48 <= code && code < 58) || code == '+' || code == '-' || code == '*' || code == '/' || code == '(' || code == ')' || code == ' ';
		}
		else
		{
			if (code == '-' && member.block.index != 0)
				return;
			if (member.block.text.size() <= member.bitNum)
				valid = (48 <= code && code < 58) || code == '-';
		}
		if (valid)
		{
			member.block.removeSelection();
			member.block.insert(code);
			member.textEdited();
			BLine2 line = member.inputLine();
			event.accept(line.end());
		}
	}
	BWidget::inputEvent(event);
}

void BSpinBox::keyPressEvent(const BKeyEvent& keyEvent)
{
	Key key = keyEvent.key();
	if (keyEvent.keyState(Key_Ctrl))
	{
		if (key == Key_C)
		{
			BString text;
			member.block.collect(text);
			BSystem::SetClippedText(text);
			keyEvent.accept(true);
		}
		else if (key == Key_A)
		{
			member.block.index = 0;
			member.block.count = member.block.cells.size();
			member.blinkShown = false;
			this->fresh();
			keyEvent.accept(true);
		}
		else if (member.readOnly == false)
		{
			if (key == Key_V)
			{
				if (member.block.removeSelection())
				{
					member.editing = true;
					member.dirty = true;
				}
				BString text;
				if (BSystem::GetClippedText(text))
				{
					text.trimmed();
					member.block.insert(text);
					member.textEdited();
					this->fresh();
				}
				keyEvent.accept(true);
			}
			else if (key == Key_X)
			{
				BString text;
				if (member.block.index >= 0)
				{
					if (member.block.count)
						member.block.collect(text);
					else
						text = member.block.text;
				}
				BSystem::SetClippedText(text);
				if (member.block.removeSelection())
				{
					member.textEdited();
					this->fresh();
				}
				keyEvent.accept(true);
			}
		}
	}
	if (member.readOnly == false)
	{
		if (key == Key_Enter)
		{
			keyEvent.accept(true);
			if (member.editing)
			{
				member.editing = false;
				member.block.text.reset(member.value);
				member.block.reset();
				member.block.count = member.block.cells.size();
				member.dirty = true;
				emit(Signal_EditFinished, member.value);
			}
			member.blinkShown = false;
			this->fresh();
		}
		else if (key == Key_Left)
		{
			if (member.block.count)
			{
				member.block.count = 0;
				this->fresh();
			}
			else if (member.block.index > 0)
			{
				member.block.index--;
				this->fresh();
			}
			member.blinkTick = keyEvent.time();
			keyEvent.accept(true);
		}
		else if (key == Key_Right)
		{
			if (member.block.count)
			{
				member.block.index += member.block.count;
				member.block.count = 0;
				this->fresh();
			}
			else if (member.block.index < member.block.cells.size())
			{
				member.block.index++;
				this->fresh();
			}
			member.blinkTick = keyEvent.time() + (BReal)0.5;
			keyEvent.accept(true);
		}
		else if (key == Key_Back)
		{
			if (member.block.count)
			{
				if (member.block.removeSelection())
				{
					member.textEdited();
				}
			}
			else
			{
				member.block.index--;
				if (member.block.remove(member.block.index))
				{
					member.textEdited();
				}
			}
			member.blinkTick = keyEvent.time() + (BReal)0.5;
			keyEvent.accept(true);
		}
		else if (key == Key_Delete)
		{
			if (member.block.count)
			{
				if (member.block.removeSelection())
				{
					member.textEdited();
				}
			}
			else
			{
				if (member.block.remove(member.block.index))
				{
					member.textEdited();
				}
			}
			member.blinkTick = keyEvent.time() + (BReal)0.5;
			keyEvent.accept(true);
		}
	}
	BWidget::keyPressEvent(keyEvent);
}
void BSpinBox::keyReleaseEvent(const BKeyEvent& keyEvent)
{
	this->fresh();
	BWidget::keyReleaseEvent(keyEvent);
}

void BSpinBox::mouseEnterEvent(const BMouseEvent& mouseEvent)
{
	if (member.hoverIndex != -1)
	{
		member.hoverIndex = -1;
	}
	member.hovered = true;
	this->fresh();
	BWidget::mouseEnterEvent(mouseEvent);
}
void BSpinBox::mouseLeaveEvent(const BMouseEvent& mouseEvent)
{
	if (member.hoverIndex != -1)
	{
		member.hoverIndex = -1;
	}
	member.hovered = false;
	this->fresh();
	BWidget::mouseLeaveEvent(mouseEvent);
}

void BSpinBox::mousePressEvent(const BMouseEvent& mouseEvent)
{
	if (mouseEvent.time() - member.focusedTime < (BReal)0.3)
		return;
	if (mouseEvent.button() == Button_Left)
	{
		if (member.block.count > 0 || member.block.index != member.hoverIndex)
		{
			member.block.index = member.hoverIndex;
			member.block.count = 0;
			member.blinkTick = mouseEvent.time();
			this->fresh();
		}
		member.pressed = true;
		member.pressedIndex = member.hoverIndex;
	}
	BWidget::mousePressEvent(mouseEvent);
}
void BSpinBox::mouseReleaseEvent(const BMouseEvent& mouseEvent)
{
	if (mouseEvent.button() == Button_Left)
	{
		member.pressed = false;
		this->fresh();
	}
	BWidget::mouseReleaseEvent(mouseEvent);
}
void BSpinBox::mouseMoveEvent(const BMouseEvent& mouseEvent)
{
	const BPoint& pos = mouseEvent.pos();
	int index = member.block.indexAt(pos);
	if (member.hoverIndex != index)
	{
		member.hoverIndex = index;
	}
	if (member.pressed)
	{
		int inputCount = member.pressedIndex - member.hoverIndex;
		if (member.block.index != member.hoverIndex || member.block.count != inputCount)
		{
			member.block.index = member.hoverIndex;
			member.block.count = inputCount;
			this->fresh();
		}
	}
	if (mouseEvent.button() == Button_None && this->focusable())
	{
		const BRect& crect = this->centerRect();
		if (crect.contain(pos))
			this->setCursor(Cursor_Ibeam);
		else
			this->setCursor(Cursor_None);
	}
	BWidget::mouseReleaseEvent(mouseEvent);
}

void BSpinBox::mouseWheelEvent(const BMouseEvent& mouseEvent)
{
	if (member.wheelEnabled && !member.readOnly)
	{
		if (mouseEvent.wheel() > 0)
		{
			if (this->setValue(member.value + member.step))
			{
				emit(Signal_ValueEdited, member.value);
				emit(Signal_EditFinished, member.value);
			}
		}
		else
		{
			if (this->setValue(member.value - member.step))
			{
				emit(Signal_ValueEdited, member.value);
				emit(Signal_EditFinished, member.value);
			}
		}
		mouseEvent.accept();
	}
	BWidget::mouseWheelEvent(mouseEvent);
}
void BSpinBox::mouseDoubleClickEvent(const BMouseEvent& mouseEvent)
{
	if (mouseEvent.button() == Button_Left)
	{
		member.block.index = 0;
		member.block.count = member.block.cells.size();
		this->fresh();
	}
	BWidget::mouseDoubleClickEvent(mouseEvent);
}

